package cn.seaboot.admin.mvc.response;

import cn.seaboot.admin.mvc.Problem;
import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.core.FastJsonUtils;
import cn.seaboot.commons.exception.BizException;
import cn.seaboot.commons.exception.ServiceException;
import cn.seaboot.commons.file.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.sql.SQLException;

/**
 * Controller异常切面
 * <p>
 * 已经指定了包名，针对 seaboot 域名下的所有 Controller 有效，
 * 每个服务需要按照选用的依赖，按照需求配置此文件。
 *
 * 当前代码极度依赖 slf4j，如果 slf4j 配置失效，则会丢失日志。
 *
 * @see Logger
 * @author Mr.css
 * @version 2020-12-15 16:45
 */
@ControllerAdvice(basePackages = "cn.seaboot")
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
    private final Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class);

    private final Feedback feedback = new FeedbackJson();

    /**
     * write logger
     *
     * @param request -
     * @param ex      -
     */
    protected void error(HttpServletRequest request, Exception ex) {
        logger.error("REQUEST URL-->:{}", request.getServletPath());
        logger.error("PARAMETER  -->:{}", FastJsonUtils.toJSONString(CommonUtils.getRequestParams(request)));
        logger.error("Exception  :", ex);
    }

    /**
     * write logger
     *
     * @param request -
     * @param ex      -
     */
    protected void debug(HttpServletRequest request, Exception ex) {
        logger.debug("REQUEST URL-->:{}", request.getServletPath());
        logger.debug("PARAMETER  -->:{}", FastJsonUtils.toJSONString(CommonUtils.getRequestParams(request)));
        logger.debug("Exception  :", ex);
    }

    /**
     * {@inheritDoc}
     */
    @ExceptionHandler
    public ResponseEntity<Problem> exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException {
        response.setContentType(MediaType.APPLICATION_PROBLEM_JSON_UTF8_VALUE);
        Problem result = handler(request, response, ex);
        return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * {@inheritDoc}
     */
    public Problem handler(HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException {

        // 常规异常，很多可预见的异常
        if (ex instanceof BizException) {
            // 业务正常终止，异常栈没有什么有用内容
            this.debug(request, ex);
            return feedback.handlerBizException(request, response, (BizException) ex);
        }else if (ex instanceof MissingServletRequestParameterException) {
            // 请求参数缺失，来源 spring-bind
            this.debug(request, ex);
            return feedback.handlerMissingServletRequestParameterException(request, response, (MissingServletRequestParameterException) ex);
        } else if (ex instanceof MethodArgumentNotValidException){
            // 请求参数校验失败，来源 spring-bind
            this.debug(request, ex);
            return feedback.handlerMethodArgumentNotValidException(request,response, (MethodArgumentNotValidException) ex);
        } else if (ex instanceof BindException) {
            // 请求参数校验失败，来源：spring.validation
            this.debug(request, ex);
            return feedback.handlerBindException(request, response, (BindException) ex);
        } else if (ex instanceof ConstraintViolationException) {
            // 请求参数校验失败，来源：javax.validation
            this.debug(request, ex);
            return feedback.handlerConstraintViolationException(request, response, (ConstraintViolationException) ex);
        }

        // 一些未处理的异常
        this.error(request, ex);
        if (ex instanceof ServiceException) {
            // 明确的业务异常，但是需要很详细的异常堆栈信息
            return feedback.handlerServiceException(request, response, (ServiceException) ex);
        } else if (ex instanceof SQLException) {
            // 未处理的 SQL 异常
            return feedback.handlerSQLException(request, response, (SQLException) ex);
        } else if (ex instanceof IOException) {
            // 未处理的 IO 异常
            return feedback.handlerIOException(request, response, (IOException) ex);
        } else if (ex instanceof AccessDeniedException) {
            // 用户鉴权失败，一般由 security 内部处理，不会在这里触发
            return feedback.handlerAccessDeniedException(request, response, (AccessDeniedException) ex);
        }  else {
            // 未知的异常
            return feedback.handlerException(request, response, ex);
        }
    }
}
